package web

import (
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strconv"

	"github.com/google/uuid"
	lru "github.com/hashicorp/golang-lru"
)

// 与框架核心解耦
// 只是作为一个内置的路由处理方法
type FileUploader struct {
	FileField   string
	DstPathFunc func(*multipart.FileHeader) string
}

func (f *FileUploader) Handle() HandlerFunc {
	return func(ctx *Context) {
		file, fileHeader, err := ctx.Req.FormFile(f.FileField)
		if err != nil {
			ctx.RespStatusCode = http.StatusBadRequest
			ctx.RespData = []byte("上传失败")
			return
		}
		defer file.Close()

		dst := f.DstPathFunc(fileHeader)
		dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o666)
		if err != nil {
			ctx.RespStatusCode = http.StatusInternalServerError
			ctx.RespData = []byte("上传失败")
			return
		}
		defer dstFile.Close()

		_, err = io.CopyBuffer(dstFile, file, nil)
		if err != nil {
			ctx.RespStatusCode = http.StatusInternalServerError
			ctx.RespData = []byte("上传失败")
			return
		}

		ctx.RespStatusCode = http.StatusOK
		ctx.RespData = []byte("上传成功")
	}
}

type FileUploaderOption func(*FileUploader)

func NewFileUploader(opts ...FileUploaderOption) *FileUploader {
	res := &FileUploader{
		FileField: "file",
		DstPathFunc: func(fh *multipart.FileHeader) string {
			return filepath.Join("upload", uuid.New().String())
		},
	}

	for _, o := range opts {
		o(res)
	}

	return res
}

type FileDownloader struct {
	Dir string
}

func (f *FileDownloader) Handle() HandlerFunc {
	return func(ctx *Context) {
		s := ctx.QueryValue("file")
		if s.err != nil {
			ctx.RespStatusCode = http.StatusBadRequest
			ctx.RespData = []byte("找不到目标文件")
			return
		}
		s.val = filepath.Clean(s.val)
		dst := filepath.Join(f.Dir, s.val)
		fn := filepath.Base(dst)

		header := ctx.Resp.Header()
		header.Set("Content-Disposition", "attachment;filename="+fn)
		header.Set("Content-Description", "File Transfer")
		header.Set("Content-Type", "application/octet-stream")
		header.Set("Content-Transfer-Encoding", "binary")
		header.Set("Expires", "0")
		header.Set("Cache-Control", "must-revalidate")
		header.Set("Pragma", "public")
		http.ServeFile(ctx.Resp, ctx.Req, dst)
	}
}

type StaticResourceHandler struct {
	dir               string
	cache             *lru.Cache
	maxSize           int
	extContentTypeMap map[string]string
}

type StaticResourceHandlerOption func(*StaticResourceHandler)

func NewStaticResourceHandler(dir string, opts ...StaticResourceHandlerOption) (*StaticResourceHandler, error) {
	c, err := lru.New(1000)
	if err != nil {
		return nil, err
	}
	res := &StaticResourceHandler{
		dir:     dir,
		cache:   c,
		maxSize: 1024 * 1024 * 10,
		extContentTypeMap: map[string]string{
			"jpeg": "application/jpeg",
			"jpg":  "application/jpeg",
			"png":  "application/png",
			"pdf":  "application/pdf",
			"gif":  "application/gif",
		},
	}

	for _, o := range opts {
		o(res)
	}
	return res, nil
}

func StaticResourceWithMaxSize(maxSize int) StaticResourceHandlerOption {
	return func(srh *StaticResourceHandler) {
		srh.maxSize = maxSize
	}
}
func StaticResourceWithCache(c *lru.Cache) StaticResourceHandlerOption {
	return func(srh *StaticResourceHandler) {
		srh.cache = c
	}
}
func StaticResourceWithExtension(extMap map[string]string) StaticResourceHandlerOption {
	return func(srh *StaticResourceHandler) {
		for ext, contentType := range extMap {
			srh.extContentTypeMap[ext] = contentType
		}
	}
}

func (s *StaticResourceHandler) Handle(ctx *Context) {
	sv := ctx.PathValue("file")
	if sv.err != nil {
		ctx.RespStatusCode = http.StatusBadRequest
		ctx.RespData = []byte("路径错误")
		return
	}

	dst := filepath.Join(s.dir, sv.val)
	ext := filepath.Ext(dst)
	header := ctx.Resp.Header()

	if data, ok := s.cache.Get(sv.val); ok {
		header.Set("Content-Type", s.extContentTypeMap[ext[1:]])
		header.Set("Content-Length", strconv.Itoa(len(data.([]byte))))

		ctx.RespData = data.([]byte)
		ctx.RespStatusCode = http.StatusOK
		return
	}

	data, err := os.ReadFile(dst)
	if err != nil {
		ctx.RespStatusCode = http.StatusInternalServerError
		ctx.RespData = []byte("服务器错误")
		return
	}
	if len(data) < s.maxSize {
		s.cache.Add(sv.val, data)
	}

	header.Set("Content-Type", s.extContentTypeMap[ext[1:]])
	header.Set("Content-Length", strconv.Itoa(len(data)))

	ctx.RespData = data
	ctx.RespStatusCode = http.StatusOK
}
